package khiem.java.sample.genericgrapth;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author khiem
 *
 * @param <T>
 */
public class GenericGraph<T extends GraphNode> {

  private String name;
  private List<T> nodes = new ArrayList<T>();
  // maps node name to list of its version, for example {"a": {"1.0.0": a1, "2.0.1": a2}, "b": {"1.0": b1, "3.0": b3}}
  private Map<String, Map<String, T>> nodeNames = new HashMap<>();
  
  List<T> installedNodes = new ArrayList<T>();
  Map<String, T> installedNodesMap = new HashMap<String, T>();
    
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public List<T> getNodes() {
    return nodes;
  }
  public Map<String, Map<String, T>> getNodeNames() {
    return nodeNames;
  }
  public void setNodeNames(Map<String, Map<String, T>> nodeNames) {
    this.nodeNames = nodeNames;
  }
  public void setNodes(List<T> nodes) {
    this.nodes = nodes;
  }
  
  public List<T> getInstalledNodes() {
    return installedNodes;
  }
  public void setInstalledNodes(List<T> installedNodes) {
    this.installedNodes = installedNodes;
  }
  
  /**
   * Adds a node to graph
   * @param node node to add
   */
  public void addNode(T node) {
    if (node.getName() == null || node.getName().trim().isEmpty()) {
      throw new IllegalArgumentException("Node name must not be empty.");
    }
    if (node.getVersion() == null || node.getVersion().trim().isEmpty()) {
      throw new IllegalArgumentException("Node version must not be empty.");
    }
    
    if (nodeNames.keySet().contains(node.getName())) {
      if (nodeNames.get(node.getName()).containsKey(node.getVersion())) {
        throw new IllegalArgumentException("A node with same name and version has been used in this graph.");
      }
      nodeNames.get(node.getName()).put(node.getVersion(), node);
    } else {
      Map<String, T> nodeVersions = new HashMap<String, T>();
      nodeVersions.put(node.getVersion(), node);
      nodeNames.put(node.getName(), nodeVersions);
    }
    
    nodes.add(node);
  }
  
  /**
   * Add series of node to the graph
   * @param nodes 
   */
  public void addAll(T... nodes) {
    for (T node : nodes) {
      this.addNode(node);
    }
  }
  
  /**
   * Removes a node from the graph
   * @param node
   */
  public void removeNode(T node) {
    if (nodeNames.keySet().contains(node.getName())) {
      nodes.remove(node);
      nodeNames.get(node.getName()).remove(node.getVersion());
    }
  }
  
  /**
   * @param node
   * @return
   */
  public boolean contains(T node) {
    return this.nodes.contains(node);
  }
  
  /**
   * @param node
   */
  public void addInstalledNode(T node) {
    if (node.getName() == null || node.getName().trim().isEmpty()) {
      throw new IllegalArgumentException("Node name must not be empty.");
    }
    if (node.getVersion() == null || node.getVersion().trim().isEmpty()) {
      throw new IllegalArgumentException("Node version must not be empty.");
    }
    
    if (installedNodesMap.containsKey(node.getName())) {
      if (installedNodesMap.get(node.getName()).getVersion().equals(node.getVersion())) {
        // do nothing
      } else {
        installedNodes.remove(installedNodesMap.get(node.getName()));
        installedNodes.add(node);
        installedNodesMap.put(node.getName(), node);
      }
    } else {
      installedNodes.add(node);
      installedNodesMap.put(node.getName(), node);
    }
    
  }
  
  public void addInstalledNodes(T... nodes) {
    for (T node : nodes) {
      addInstalledNode(node);
    }
  }
  
  public boolean isInstalled(String nodeName) {
    return installedNodesMap.containsKey(nodeName);
  }
  
  public T installedNode(String nodeName) {
    return installedNodesMap.containsKey(nodeName)?installedNodesMap.get(nodeName):null;
  }
  
  public String installedVersion(String nodeName) {
    return installedNodesMap.containsKey(nodeName)?installedNodesMap.get(nodeName).getVersion():null;
  }
  
  public boolean isIntalled(T node) {
    return installedNodes.contains(node);
  }
  
  /**
   * @param node
   * @return
   */
  public List<GraphNode> isBrokenBy(Collection<? extends T> inputNodes, T node) {
    List<GraphNode> brokens = new ArrayList<>();
    for (T installed : inputNodes) {
      for (Dependency dep : installed.getDependencies()) {
        if (!dep.validateNode(node)) {
          brokens.add(installed);
          break;
        }        
      }
    }
    return brokens;
  }
}


